#region Using References

using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;

#endregion

namespace Beaker.OpenCube.Game.Blocks
{
	/// <summary>
	/// The base class for a physical block in the world
	/// </summary>
	[DebuggerDisplay("{Block.Block}:{Block.Data.Value}")]
	public class WorldBlock
	{
		private readonly Chunk _chunk;
		private readonly ChunkSection _section;
		private readonly short _y;
		private readonly byte _x;
		private readonly byte _z;
		private readonly short _index;

		/// <summary>
		/// Initializes a new instance of the <see cref="WorldBlock"/> class.
		/// </summary>
		/// <param name="chunk">The actual chunk this block lives in</param>
		/// <param name="y">Y Coordinate in the chunk - Up, Down</param>
		/// <param name="z">Z Coordinate in the chunk - South, North</param>
		/// <param name="x">X Coordinate in the chunk - East, West</param>
		protected internal WorldBlock( Chunk chunk, short y, byte z, byte x )
		{
			Contract.Requires<ArgumentNullException>( chunk != null );
			Contract.Requires<ArgumentOutOfRangeException>( z < 16 );
			Contract.Requires<ArgumentOutOfRangeException>( x < 16 );

			_chunk = chunk;
			_y = y;
			_z = z;
			_x = x;
			_section = GetSection();
			_index = (short)( ( _y % 16 ) << 8 | _z << 4 | _x );
		}

		[ContractInvariantMethod]
		private void ContractInvariant()
		{
			Contract.Invariant( _chunk != null );
			Contract.Invariant( _z < 16 );
			Contract.Invariant( _x < 16 );
			Contract.Invariant( _y < 4096 );
		}

		/// <summary>
		/// Gets the type of the block.
		/// </summary>
		/// <value>
		/// The type of the block.
		/// </value>
		public BlockType Type
		{
			get
			{
				if( _section == null )
					return BlockType.Air;

				short blockId = _section.Blocks[_index];
				byte[] add = _section.Add;
				if( add != null )
					blockId |= (short)( add.GetNibble( _index ).Value << 8 );

				return (BlockType)blockId;
			}
		}

		/// <summary>
		/// Gets the data of this block.
		/// </summary>
		/// <value>
		/// The data.
		/// </value>
		public Nibble Data
		{
			get
			{
				if( _section == null )
					return Nibble.Zero;

				return _section.Data.GetNibble( _index );
			}
		}

		/// <summary>
		/// Gets the block.
		/// </summary>
		/// <value>
		/// The block.
		/// </value>
		public Block Block
		{
			get { return GetBasicBlockData(); }
		}

		/// <summary>
		/// Override when the block requires some extra data to become a actual Block (i.e. BlockType with Data 0 is not enough to determine the block, like wool, or wood)
		/// </summary>
		/// <value>
		/// The basic block data
		/// </value>
		protected internal virtual Block GetBasicBlockData()
		{
			return Block.Get( Type, Data );
		}

		/// <summary>
		/// Gets the amount of block-emitted light.
		/// </summary>
		/// <value>
		/// The amount of block-emitted light.
		/// </value>
		public Nibble BlockLight
		{
			get
			{
				// TODO: Actually determine if there is a section above, it might be sunlight or dark
				if( _section == null )
					return Nibble.Zero;

				return _section.BlockLight.GetNibble( _index );
			}
		}

		/// <summary>
		/// Gets the amount of sunlight or moonlight hitting the block.
		/// </summary>
		/// <value>
		/// The amount of sunlight or moonlight hitting the block.
		/// </value>
		public Nibble SkyLight
		{
			get
			{
				// TODO: Actually determine if there is a section above, it might be sunlight or dark
				if( _section == null )
					return Nibble.Zero;

				return _section.SkyLight.GetNibble( _index );
			}
		}

		/// <summary>
		/// Gets the section this block exists in
		/// </summary>
		/// <returns>The section, or <c>null</c> when there is no section (air)</returns>
		private ChunkSection GetSection()
		{
			int ySection = _y / 16;
			return _chunk.Sections.FirstOrDefault( s => s.Y == ySection );
		}
	}

}